package com.game.center.service;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.alibaba.fastjson.JSON;
import com.game.common.utils.GameUUIDUtil;
import com.game.common.utils.JWTUtil;
import com.game.common.utils.JWTUtil.TokenContent;
import com.game.center.logicconfig.GameCenterConfig;
import com.game.center.logicconfig.EnumRedisKey;
import com.game.center.messages.GameCenterError;
import com.game.center.messages.request.LoginParam;
import com.game.center.messages.response.VoUserAccount;
import com.game.framework.dao.IGameDaoExecutorFactory;
import com.game.framework.dao.entity.UserBasic;
import com.game.framework.dao.redis.CommonRedisKeyConifg;
import com.game.framework.dao.redis.IRedisKeyConfig;
import com.game.framework.dao.redis.RedisCacheTemplate;
import com.game.network.message.error.GameErrorException;

import io.netty.util.concurrent.EventExecutor;

public abstract class AbstractUserLoginService implements IUserLoginService {

	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private RedisCacheTemplate redisCacheTemplate;
	@Autowired
	private GameCenterConfig gameCenterConfig;
	@Autowired
	private IGameDaoExecutorFactory gameDaoExecutorFactory;
	private Logger logger = LoggerFactory.getLogger(AbstractUserLoginService.class);

	/**
	 * 根据用户名和密码登陆
	 * <p>
	 * Description: 返回登陆成功之后客户端可能显示的信息，如果有新的项目有需要添加额外的信息，可以继承VoUserAccount扩展
	 * </p>
	 * 
	 * @param loginParam
	 * @return
	 * @author wgs
	 * @date 2019年8月29日 下午8:24:29
	 *
	 */
	public VoUserAccount loginByUserName(LoginParam loginParam) {
		String userName = loginParam.getUserName();
		userName = userName.intern();
		UserBasic userBasic = this.getUserAccount(userName);
		VoUserAccount userAccount = null;
		if (userBasic != null) {
			if (userBasic.getPassword().equals(loginParam.getPassword())) {
				userAccount = loginSuccess(userBasic);
				this.updateUserAccountExpire(userBasic);
				logger.debug("user {} 登陆成功", userAccount.getUserName());
			} else {
				throw GameErrorException.newBuilder(GameCenterError.PASSWORD_ERROR).build();
			}
		} else if (isAutoRegiter()) {
			synchronized (userName) {
				userBasic = this.getUserAccount(userName);
				if (userBasic != null) {
					throw GameErrorException.newBuilder(GameCenterError.USER_ACCOUNT_EXIST).build();
				}
				long userId = this.getNextUserId();// 使用redis自增保证userId全局唯一
				// 用户不存在，自动注册
				if (loginParam.isUserNameLogin()) {
					String openId = GameUUIDUtil.getUId();
					loginParam.setOpenId(openId);
				}
				this.setUserAccountIDMapper(loginParam.getUserName(), userId);
				userBasic = this.register(loginParam, userId);
				userAccount = loginSuccess(userBasic);
				logger.debug("user {} 注册成功,userId:{}", loginParam.getUserName(), userId);
			}
		}
		if (userAccount != null) {
			String token = this.createUserToken(userName, userAccount.getUserId(), loginParam.getLoginType());
			userAccount.setToken(token);// 这里使用JWT生成Token
			return userAccount;
		}
		throw GameErrorException.newBuilder(GameCenterError.PASSWORD_ERROR).build();
	}

	private UserBasic register(LoginParam param, long userId) {
		UserBasic userBasic = this.createRegister(param, userId);
		this.updateAccount(userBasic);
		return userBasic;
	}

	/**
	 * 设置用户名和id的映射
	 * 
	 * @param userAccount
	 */
	public void setUserAccountIDMapper(String userAccount, long userId) {
		IRedisKeyConfig enumRedisKey = CommonRedisKeyConifg.USER_ACCOUNT_MAPPER_ID;
		String key = enumRedisKey.getKey(userAccount);
		redisTemplate.opsForValue().set(key, String.valueOf(userId));
	}

	protected abstract UserBasic getUserAccount(String userName);

	protected abstract boolean isAutoRegiter();

	protected abstract VoUserAccount loginSuccess(UserBasic account);

	protected abstract UserBasic createRegister(LoginParam loginParam, long userId);

	public String createUserToken(String userName, long userId, int loginType) {
		TokenContent tokenContent = new TokenContent();
		tokenContent.setLoginType(loginType);
		tokenContent.setUserId(userId);
		tokenContent.setOpenId(userName);
		return JWTUtil.createToken(tokenContent, Duration.ofDays(gameCenterConfig.getUserTokenExpire()));// 有效期
	}

	/**
	 * 
	 * <p>
	 * Description:更新用户redis缓存的过期时间，登陆就延长
	 * </p>
	 * 
	 * @param userAccount
	 * @author wang guang shuai
	 * @date 2020年1月10日 下午3:59:16
	 *
	 */
	public void updateUserAccountExpire(UserBasic userAccount) {
		gameDaoExecutorFactory.getEventExecutorGroup().select(userAccount.getUserId()).execute(() -> {
			try {
				// 更新名字和id的映射过期时间
				updateRedisKeyExpire(CommonRedisKeyConifg.USER_ACCOUNT_MAPPER_ID, userAccount.getAccount());
				// 更新用户信息过期时间
				updateRedisKeyExpire(CommonRedisKeyConifg.USER_ACCOUNT, userAccount.getUserId());
			} catch (Exception e) {
				logger.error("异步更新用户缓存信息失败", e);
			}
		});
	}


	public void updateRedisKeyExpire(CommonRedisKeyConifg redisKey, Object param) {
		String userNameIDKey = redisKey.getKey(param.toString());
		Duration expire = redisKey.getExpire();
		if (expire != null) {
			redisTemplate.expire(userNameIDKey, expire.toMillis(), TimeUnit.MINUTES);
		}
	}

	public long getNextUserId() {
		String key = EnumRedisKey.USER_ID_INCR.getKey();
		long userId = redisTemplate.opsForValue().increment(key);
		return userId;
	}

	protected long getUserIdByUserName(String userName) {
		IRedisKeyConfig enumRedisKey = CommonRedisKeyConifg.USER_ACCOUNT_MAPPER_ID;
		String key = enumRedisKey.getKey(userName);
		String userId = redisCacheTemplate.getValue(key, userName, enumRedisKey.getExpire(), p -> {
			UserBasic result = this.getUserFromDBByAccount(userName);
			if (result != null) {
				String accountRedisKey = CommonRedisKeyConifg.USER_ACCOUNT.getKey(String.valueOf(result.getUserId()));
				redisTemplate.opsForValue().set(accountRedisKey, JSON.toJSONString(result));
			}
			return result == null ? null : String.valueOf(result.getUserId());
		});
		return userId == null ? 0 : Long.parseLong(userId);
	}

	protected abstract UserBasic getUserFromDBByAccount(String account);

	public abstract void updateAccount(UserBasic userBasic);

}
